#include "ssm_state.h"
#include <iostream>

using namespace SimpleStateMachineLib;


enum GarageDoorEvent
{
    RemoteButtonEvent = 0,
    ObstructingObjectEvent,
    FullyOpenedEvent,
    FullyClosedEvent,
    SemiOpenedTimer,
};

const char* GarageDoorEventName[] = 
{
    "RemoteButton",
    "ObstructingObjectEvent",
    "FullyOpenedEvent",
    "FullyClosedEvent",
    "SemiOpenedTimer"
};


const char* getEventName(const GarageDoorEvent& event)
{
    return GarageDoorEventName[event];
}

template <class TEventType>
class Timer
{
public:
    void start(){}
    void cancel(){}
    TEventType getEvent() const
    {
    	return SemiOpenedTimer;
    }
};

template <class TStateMachine>
class TimedState: public State<TStateMachine>
{
public:
    typedef typename TStateMachine::EventType EventType;
private:
    Timer<EventType> m_timer;
public:

    void onEntry()
    {
        m_timer.start();
    }

    void onExit()
    {
        m_timer.cancel();
    }

    bool handleEvent(const EventType& event)
    {
        if(event == m_timer.getEvent())
        {
            onTimeout();
        }
        return true;
    }

    virtual void onTimeout()
    {
    }
};

class GarageDoorStateMachine: 
    public StateMachine<GarageDoorStateMachine, 
        GarageDoorEvent>
{   
    class ClosedState: public StateType
    {
    public:
        bool handleEvent(const EventType& event)
        {
            if(event == RemoteButtonEvent)
            {
                transition(machine()->m_OpeningState);
            }
            return true;
        }

        const char* toString() const
        {
            return "ClosedState";
        }

    };

    class OpeningState: public StateType
    {
    public:
        bool handleEvent(const EventType& event)
        {
            if(event == RemoteButtonEvent)
            {
                transition(machine()->m_SemiOpenedState);
            }
            else if(event == FullyOpenedEvent)
            {
                transition(machine()->m_OpenedState);
            }
            return true;
        }
        const char* toString() const
        {
            return "OpeningState";
        }
    };

    class SemiOpenedState: public TimedState<GarageDoorStateMachine>
    {
    public:
        bool handleEvent(const EventType& event)
        {
            if(event == RemoteButtonEvent)
            {
                if(!machine()->hasObstructedObject())
                {
                    transition(machine()->m_ClosingState);
                }
            }
            return TimedState<GarageDoorStateMachine>::handleEvent(event);
        }

        void onTimeout()
        {
            if(!machine()->hasObstructedObject())
            {
                transition(machine()->m_ClosingState);
            }
            else
            {
                transition(*this);
            }
        }
        const char* toString() const
        {
            return "SemiOpenedState";
        }
    };

    class ClosingState: public StateType
    {
    public:
        bool handleEvent(const EventType& event)
        {
            if(event == RemoteButtonEvent 
                || event == ObstructingObjectEvent)
            {
                transition(machine()->m_SemiClosedState);
            }
            if(event == FullyClosedEvent)
            {
                transition(machine()->m_ClosedState);
            }
            return true;
        }

        const char* toString() const
        {
            return "ClosingState";
        }
    };

    class SemiClosedState: public StateType
    {
    public:
        bool handleEvent(const EventType& event)
        {
            if(event == RemoteButtonEvent)
            {
                transition(machine()->m_OpeningState);
            }
            return true;
        }

        const char* toString() const
        {
            return "SemiClosedState";
        }
    };

    class OpenedState: public StateType
    {
    public:
        bool handleEvent(const EventType& event)
        {
            if(event == RemoteButtonEvent)
            {
                if(!machine()->hasObstructedObject())
                {
                    transition(machine()->m_ClosingState);
                }
            }
            return true;
        }
        const char* toString() const
        {
            return "OpenedState";
        }
    };

    ClosedState m_ClosedState;
    OpenedState m_OpenedState;
    ClosingState m_ClosingState;
    OpeningState m_OpeningState;
    SemiOpenedState m_SemiOpenedState;
    SemiClosedState m_SemiClosedState;

public:

    GarageDoorStateMachine()
    {
        m_ClosedState.setStateMachine(this);
        m_OpenedState.setStateMachine(this);
        m_ClosingState.setStateMachine(this);
        m_OpeningState.setStateMachine(this);
        m_SemiOpenedState.setStateMachine(this);
        m_SemiClosedState.setStateMachine(this);

        setInitialState(m_ClosedState);
    }

    bool hasObstructedObject() const
    {
        return false;
    }
    
    
    void logTransition(const EventType& event, 
        const StateType& fromState, 
        const StateType& toState)
    {
        std::cout
            << "On "
            << getEventName(event)
            << " transition from " 
            << fromState.toString() 
            << " to " 
            << toState.toString() 
            << std::endl;
    }

};

int main(int argc, char** argv)
{
    GarageDoorStateMachine sm;

    sm.onEntry();
    sm.handleEvent(RemoteButtonEvent);
    sm.handleEvent(FullyOpenedEvent);
    sm.handleEvent(RemoteButtonEvent);
    sm.handleEvent(ObstructingObjectEvent);
    return 0;
}
